﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Http;
using System.Timers;

namespace TiebaSpider.Core
{
    public class Crawler
    {
        public string TargetURL { get; set; }
        public int TimeOutThreshold { get; set; }

        public event EventHandler<CrawlerEventArgs> CrawlerEvent;

        private HttpClient client;
        private Timer timer;
        private bool cancelSign;

        public Crawler()
        {
            TargetURL = "";
            TimeOutThreshold = (App.Current as App).SetupController.AppOptions.CrawlerTimeout;
            cancelSign = false;
            client = new HttpClient();
            timer = new Timer();
            timer.Elapsed += TimeOutEventHandler;
        }

        private void TimeOutEventHandler(object sender, ElapsedEventArgs arg)
        {
            cancelSign = true;
            timer.Enabled = false;
        }

        private void RaiseCrawlerEvent(int code, string message)
        {
            if (CrawlerEvent != null)
            {
                CrawlerEvent(this, new CrawlerEventArgs(code, message));
            }
        }

        private void ResetTimer()
        {
            timer.Enabled = false;
            cancelSign = false;
            timer.Interval = TimeOutThreshold;
        }

        public async Task<string> GetStringContent()
        {
            string result = string.Empty;
            try
            {
                if (TargetURL.Length == 0) throw new TargetNotSetException("Target URL does NOT set yet.");
                HttpResponseMessage response = await client.GetAsync(TargetURL);
                timer.Enabled = true;
                response.EnsureSuccessStatusCode();
                result = await response.Content.ReadAsStringAsync();
                while (result.Length == 0)
                {
                    if (cancelSign) throw new RetreivingCanceledException("Request Time out.");
                }
                RaiseCrawlerEvent(200, "Request completed");
            }
            catch (TargetNotSetException tnse)
            {
                RaiseCrawlerEvent(201, tnse.Message);
            }
            catch (HttpRequestException hre)
            {
                RaiseCrawlerEvent(500, hre.Message);
            }
            catch (RetreivingCanceledException rce)
            {
                RaiseCrawlerEvent(403, rce.Message);
            }
            return result;
        }

        public async Task<byte[]> GetByteArrayContent()
        {
            byte[] result = null;
            try
            {
                if (TargetURL.Length == 0) throw new TargetNotSetException("Target URL does NOT set yet.");
                HttpResponseMessage response = await client.GetAsync(TargetURL);
                timer.Enabled = true;
                response.EnsureSuccessStatusCode();
                result = await response.Content.ReadAsByteArrayAsync();
                while (result.Length == 0)
                {
                    if (cancelSign) throw new RetreivingCanceledException("Request Time out.");
                }
                RaiseCrawlerEvent(200, "Request completed");
            }
            catch (TargetNotSetException tnse)
            {
                RaiseCrawlerEvent(201, tnse.Message);
            }
            catch (HttpRequestException hre)
            {
                RaiseCrawlerEvent(500, hre.Message);
            }
            catch (RetreivingCanceledException rce)
            {
                RaiseCrawlerEvent(403, rce.Message);
            }
            return result;
        }
    }

    public class CrawlerEventArgs : EventArgs
    {
        public int EventCode { get; private set; }
        public string EventMessage { get; private set; }

        public CrawlerEventArgs(int code, string message)
        {
            this.EventCode = code;
            this.EventMessage = message;
        }
    }

    [Serializable]
    public class TargetNotSetException : ApplicationException
    {
        public TargetNotSetException() { }
        public TargetNotSetException(string message) : base(message) { }
        public TargetNotSetException(string message, Exception inner) : base(message, inner) { }
    }

    [Serializable]
    public class RetreivingCanceledException : ApplicationException
    {
        public RetreivingCanceledException() { }
        public RetreivingCanceledException(string message) : base(message) { }
        public RetreivingCanceledException(string message, Exception inner) : base(message, inner) { }
    }
}
